<?PHP
/*
 * $Id: barge-db.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - Web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\db;

use \Exception;
use \PDO;

use barge\ClassLoader;
use barge\System;

use barge\logging\LogFactory;
use barge\utils\ArrayList;
use barge\utils\Bean;
use barge\utils\BeanUtils;
use barge\utils\HashMap;
use barge\utils\String;
use barge\utils\Timestamp;

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class SQLException extends Exception
{
}

/**
 * @author Kenji Namba
 * @version 1.0
 * @since Barge 1.0 2006/08/12
 */
class DaoManager
{

	/**
	 * @var ConnectionFactory
	 */
	private $conFactory;

//	/**
//	 * @var string DAO実装クラスの検索パス
//	 */
//	private $daoImplPattern;

	/**
	 * @var Config
	 */
	private $configs;

//	/**
//	 * @var HashMap
//	 */
//	private $daoConfigs;

	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * @var HashMap
	 * @since 1.1 2009/02/28
	 */
	private $context;

	/**
	 * A constructor.
	 *
	 * @param ConnectionFactory $connFactory
	 */
	public function __construct()
	{
		$this->log = LogFactory::getLogger();
//		$this->daoConfigs = new HashMap();
		$this->configs = System::getProperty('DaoManager');

		$connectionClass = $this->configs->getProperty('connectionClass');
		$classLoader = System::getClassLoader();
		$this->conFactory = $classLoader->newInstance($connectionClass);
//		$this->conFactory = $connFactory;
	}

	/**
	 * Start transaction
	 *
	 * <pre>
	 * $daoManager->beginTransaction();
	 * $daoManager->beginTransaction($request->getUser());
	 * </pre>
	 *
	 * @param HashMap|Bean $context {@link BeanUtils}でプロパティー参照可能なオブジェクト
	 */
	public function beginTransaction($context = null)
	{
		$this->context = $context;

		// Do nohing.
		$conn = $this->conFactory->getConnection();
		$conn->beginTransaction();
//		// Create new connection.
//		if ($this->conn == null) {
//			//$classLoader = System::getClassLoader();
//			//$this->conn = $classLoader->newInstance(System::getProperty('DAO', 'connection_class'),
//			new ArrayList());
//		}
	}

	/**
	 * Retunes dao implementation instance.
	 * @param string $key
	 * {@return Dao}
	 */
	public function getDao($name)
	{
		// クラス名を動的に解決
		$pattern = $this->configs->getProperty('daoInstancePattern');
		if ($pattern !== null) {
			$daoClass = String::format($pattern, array('name' => $name));
		} else {
			// TODO :
		}
//		$daoClassName = str_replace("*", $key, $this->daoImplPattern);
//		$daoClassName = String::format($this->daoImplPattern, array('name' => $key));
//		$daoClass = $this->daoConfigs->get($key);
		if ($daoClass === null) {
			$message = "DAO '{$name}' の定義が見つかりません。";
			throw new Exception($message);
		}

		$this->log->debug('DaoManager : Instance dao class '.$name.'('.$daoClass.')');

		// DB接続の取得
		$conn = $this->conFactory->getConnection();

		// DAOのインスタンスを生成する
		$classLoader = System::getClassLoader();
		$daoInstance = $classLoader->newInstance($daoClass, new ArrayList($conn, $this->context));
		return $daoInstance;
	}

	/**
	 * End transaction
	 * @since 1.0
	 */
	public function endTransaction()
	{
		$conn = $this->conFactory->getConnection();
		if ($conn !== null) {
			$conn->close();
		}
	}

	/**
	 * Commit transaction
	 * @since 1.0
	 */
	public function commit()
	{
		$conn = $this->conFactory->getConnection();
		if ($conn !== null) {
			$conn->commit();
		}
	}

	/**
	 * Rollback transaction.
	 * @since 1.0
	 */
	public function rollback()
	{
		$conn = $this->conFactory->getConnection();
		if ($conn !== null) {
			$conn->rollback();
		}
	}

	/**
	 * @return Connection
	 * @since 1.2 2008/09/16
	 */
	public function getConnection()
	{
		return $this->conFactory->getConnection();
	}
}


///**
// * カラム名からプロパティー名に変換する
// *
// * @version 1.0
// * @author Kenji Namba
// * @since Barge 1.0 2008/09/11
// * @deprecated
// */
//interface PropertyMapper
//{
//	public function columnToProperty($column);
//}
//
///**
// * @see PropertyMapper
// * @deprecated
// */
//class DefaultPropertyMapper implements PropertyMapper
//{
//	/**
//	 * @var HashMap
//	 */
//	private $cache;
//
//	/**
//	 *
//	 */
//	public function __construct()
//	{
//		$this->cache = new HashMap();
//	}
//
//	/**
//	 * @see PropertyMapper#columnToProperty
//	 */
//	public function columnToProperty($columName)
//	{
//		$converted = $this->cache->get($columName);
//		if ($converted != null) {
//			return $converted;
//		}
//		$converted = preg_replace("/(_?([a-z0-9]+))/e", "ucwords('\\2')", $columName);
//		$this->cache->put($columName, $converted);
//		return $converted;
//	}
//}


/**
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
interface ConnectionFactory
{
	/**
	 * @return Connection
	 */
	public function getConnection();
}

/**
 * Datadata access object for PHP.
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
interface Connection
{
	/**
	 * @param string $query
	 * @param ArrayList $params
	 * @param RowHandler $rsHandler
	 * @throws SQLExceotion
	 */
	public function executeQuery($query, ArrayList $params = null, RowHandler $rsHandler = null);

	/**
	 * @throws SQLExceotion
	 */
	public function beginTransaction();

	/**
	 * @param string $query
	 * @param ArrayList $params
	 * @throws SQLExceotion
	 */
	public function executeUpdate($query, ArrayList $params = null);

	/**
	 * @return void
	 */
	public function commit();

	/**
	 * @return void
	 */
	public function rollback();

	/**
	 * @return void
	 */
	public function close();

	/**
	 * @param string $seqname シーケンス名
	 * @return int
	 */
	public function getLastInsertId($seqname = null);
}

/**
 * Datadata access object for PHP.
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
interface RowHandler
{
	/**
	 * Enter description here...
	 * @return object
	 */
	public function handle($object);
}

/**
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class BeanRowHandler implements RowHandler
{
	/**
	 * @var object
	 */
	protected $proto;

	/**
	 * コンストラクタ
	 * @param stirng $className
	 */
	public function __construct($className)
	{
		$classLoader = System::getClassLoader();
		$this->proto = $classLoader->newInstance($className);
	}

	/**
	 * @param string $object
	 */
	public function handle($object)
	{
		$bean = clone $this->proto;
		$bean->__populate($object, true);
		return $bean;
	}
}

/**
 * データベース接続インターフェース、Pear::DBP実装クラス
 *
 * @see Connection
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class PearDBConnection implements Connection
{
	/**
	 * @var DB
	 */
	private $db;

	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * コンストラクタ
	 */
	public function __construct()
	{
		$this->log = LogFactory::getLogger();

		// DB.phpをインクルード
		$classLoader = System::getClassLoader();
		$classLoader->includeModule('DB.php');

		// DB接続
		$dsn = System::getProperty('DAO/database_uri');
		$this->db = DB::connect($dsn, array (
			'persistent' => false
		));
		if (DB::isError($this->db)) {
			$err_msg = "Can't connect databse. DSN => [$dsn] MSG => [" . $this->db->getMessage() . '/' . $this->db->getDebugInfo() . "]";
			$this->db = null;
			throw new SQLException($err_msg);
		}
		$this->db->autoCommit(false);
	}

	/**
	 * トランザクション開始
	 * @see Connection#beginTransaction
	 */
	public function beginTransaction()
	{
		// Do nothing.
	}

	/**
	 *
	 * @param string $query SQLクエリー
	 * @param ArrayList $params バインドパラメータ
	 * @param RowHandler $rsHandler 行ハンドラー
	 * @return ArrayList
	 */
	public function executeQuery($query, ArrayList $params = null,
	RowHandler $rsHandler = null)
	{
		// Parse binding query.
		if ($this->log->isDebugEnabled()) {
			$message = 'Query=['
			.$query
			. '] Params:['
			. ($params === null ? '' : $params->join(","))
			. ']';
			$this->log->debug($message);
		}
		$result = new ArrayList();
		$rs = $this->db->query($query, ($params !== null ? $params->toArray() : Array()));
		if (DB::isError($rs)) {
			$message = "Error in query({$rs->getMessage()}). Query:[{$query}] Params:[".
			($params == null ? null : $params->join(",")).']';
			throw new SQLException($message);
		}

		while ($row = $rs->fetchRow(DB_FETCHMODE_ASSOC)) {
			$result->add($rsHandler != null ? $rsHandler->handle($row) : new HashMap($row));
		}

		$rs->free();
		return $result;
	}

	/**
	 * 更新クエリーを実行します
	 *
	 * @param string $query SQLクエリー
	 * @param ArrayList $params バインドパラメータ
	 * @return void
	 */
	public function executeUpdate($query, ArrayList $params = null)
	{
		if ($this->log->isDebugEnabled()) {
			$message = 'Query=['
			.$query
			. '] Params:['
			. ($params === null ? '' : $params->join(","))
			. ']';
			$this->log->debug($message);
		}
		$ret = $this->db->query($query, ($params !== null ? $params->toArray() : Array()));
		if (DB::isError($ret)) {
			$message = "Error in query({$ret->getMessage()}). Query:[{$query}] Params:[".
			($params == null ? null : $params->join(",")).']';
			throw new SQLException($message);
		}
		return $ret;
	}

	/**
	 * @since 1.0
	 */
	public function commit()
	{
		$this->db->commit();
	}

	/**
	 * @since 1.0
	 */
	public function rollback()
	{
		$this->db->rollback();
	}

	/**
	 * 接続をクローズする
	 * @since 1.0
	 */
	public function close()
	{
		if ($this->db == null) {
		} else {
			if ($this->getLog()->isDebugEnabled()) {
				$this->getLog()->debug("Connection disconnect manualy.");
			}
			$this->db->disconnect();
			$this->db = null;
		}
	}

	/**
	 * @param string $seq シーケンス名
	 * @since 1.0
	 */
	public function getLastInsertId($seq = null)
	{
		return $this->db->getLastInsertId($seq);
	}

	/**
	 * A destrator.
	 *
	 * @return void
	 * @since 1.0 2006/09/11
	 */
	public function __destruct()
	{
		if ($this->db == null || DB::isError($this->db)) {
		} else {
			if ($this->log->isDebugEnabled()) {
				$this->log->debug("Connection disconnect automatic.");
			}
			$this->db->rollback();
			$this->db->disconnect();
			$this->db = null;
		}
	}
}

/**
 * データベース接続インターフェース、PDO実装クラス
 *
 * @see Connection
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class PDOConnectionFactory implements ConnectionFactory
{
	/**
	 * @var string
	 */
	private $connectionString;
	private $user;
	private $password;
	private $connection;

	public function __construct()
	{
		$configs = System::getProperty('Connection');
		$this->connectionString = $configs->getProperty('dsn');
		$this->user = $configs->getProperty('user');
		$this->password = $configs->getProperty('password');
		$this->connection = null;
	}

	/**
	 * @return Connection
	 */
	public function getConnection()
	{
		if ($this->connection === null) {
			$this->connection = new PDOConnection(
				$this->connectionString,
				$this->user,
				$this->password);
		}
		return $this->connection;
	}
}

/**
 * データベース接続インターフェース、PDO実装クラス
 *
 * @see Connection
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class PDOConnection implements Connection
{
	/**
	 * @var string
	 */
	private $connectionString;

	/**
	 * @var string
	 */
	private $user;

	/**
	 * @var string
	 */
	private $password;

	/**
	 * @var PDO
	 */
	private $pdo;

	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * コンストラクタ
	 */
	public function __construct($connectionString, $user, $password)
	{
		$this->connectionString = $connectionString;
		$this->user = $user;
		$this->password = $password;
		$this->log = LogFactory::getLogger();
	}

	/**
	 * PDOインスタンスの生成
	 * @return unknown
	 */
	private function instancePDO()
	{
		// DB接続
		$pdo = new PDO(
			$this->connectionString,
			$this->user,
			$this->password
			);
		$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);
		$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$pdo->beginTransaction();
		return $pdo;
	}

	/**
	 * @param string $query
	 * @param ArrayList $params
	 * @param RowHandler $rsHandler
	 * @return ArrayList
	 * @see Connection#executeQuery
	 */
	public function executeQuery($query, ArrayList $params = null,
		RowHandler $rsHandler = null)
	{
		// Parse binding query.
		if ($this->log->isDebugEnabled()) {
			$message = 'Query=['
			.$query
			. '] Params:['
			. ($params === null ? null : $params->join(','))
			. ']';
			$this->log->debug($message);
		}
		$result = new ArrayList();
		$stm = $this->pdo->prepare($query);
		$stm->execute($params !== null ? $params->toArray() : array());
		while ($row = $stm->fetch()) {
			$result->add($rsHandler != null ? $rsHandler->handle($row) : new HashMap($row));
		}

		return $result;
	}

	/**
	 * @param string $query SQLクエリー
	 * @param ArrayList $params バインドパラメータ
	 * @return int レコード更新件数
	 * @see Connection#executeUpdate
	 */
	public function executeUpdate($query, ArrayList $params = null)
	{
		if ($this->log->isDebugEnabled()) {
			$this->log->debug('Query=[' . $query .
			'][' . ($params !== null ? $params->join(',') : null) . ']');
		}
		$stm = $this->pdo->prepare($query);
		$stm->execute($params !== null ? $params->toArray() : array());
		return $stm->rowCount();
	}

	/**
	 * トランザクション開始
	 * @see Connection#beginTransaction
	 */
	public function beginTransaction()
	{
		if ($this->pdo === null) {
			$this->pdo = $this->instancePDO();
		} else {
			$this->pdo->beginTransaction();
		}
	}

	/**
	 * @return void
	 * @see Connection#commit
	 */
	public function commit()
	{
		$this->pdo->commit();
	}

	/**
	 * @return void
	 * @see Connection#rollback
	 */
	public function rollback()
	{
		try {
			if ($this->pdo !== null)
				$this->pdo->rollBack();
		} catch (PDOException $e) {
			$this->log->warn($e->getMessage());
		}
	}

	/**
	 * @return void
	 * @see Connection#close
	 */
	public function close()
	{
	}

	/**
	 * @param string $seq シーケンス名
	 * @return void
	 * @see Connection#getLastInsertId
	 */
	public function getLastInsertId($seq = null)
	{
		return $this->pdo->lastInsertId($seq);
	}

}

/**
 * <code>DaoManager</code>が管理するDAOクラスの規定クラス
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
abstract class Dao
{
	/**
	 * @var Connection
	 */
	private $conn;

	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * @var Logger
	 */
	private $context;

	/**
	 * コンストラクタ
	 *
	 * @param Connection $conn
	 * @param mixed $context
	 */
	public function __construct(Connection $conn, $context = null)
	{
		$this->log = LogFactory::getLogger();
		$this->conn = $conn;
		$this->context = $context;
	}

	/**
	 * @return Connection
	 */
	public function getConnection()
	{
		return $this->conn;
	}

	/**
	 * @return Logger
	 */
	protected function getLog()
	{
		return $this->log;
	}
}

/**
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.2 2006/09/11
 * @since PHP 5.3
 */
class Criteria
{
	const OP_AND = 1;
	const OP_OR = 2;

	const CP_EQUAL = 1;
	const CP_LESSTHAN = 2;
	const CP_NOTEQUALS = 3;
	const CP_GREATEREQAULS = 4;
	const CP_LESSEQALS = 5;
	const CP_LIKE = 6;
	const CP_BETWEEN = 7;
	const CP_GREATERTHAN = 8;
	const CP_NOTIN = 11;
	const CP_IN = 10;
	const CP_EXPRESSION = 12;
	const CP_CRITERIA = 99;


	/**
	 * @var ArrayList
	 */
	private $clauses = null;

	/**
	 * コンストラクタ
	 * @since 1.0 2006/09/11
	 */
	public function __construct()
	{
		$this->clauses = new ArrayList();
	}

	/**
	 * @param string $property
	 * @param mixed $value
	 * @param integer $opera
	 * @return Criteria
	 */
	public function addEquals($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(
			array('sign'=>self::CP_EQUAL,
				'property'=>$property,
				'value'=>$value,
				'join'=>$join)
			);
		return $this;
	}

	/**
	 * @param string $property
	 * @param mixed $value
	 * @param integer $opera
	 * @return Criteria
	 */
	public function addGreaterThan($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(array(
				'sign'=>self::CP_GREATERTHAN,
				'property'=>$property,
				'value'=>$value,
				'join'=>$join
				)
			);
		return $this;
	}

	/**
	 * @param string $property
	 * @param mixed $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addLessEquals($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(array(
				'sign'=>self::CP_LESSEQALS,
				'property'=>$property,
				'value'=>$value,
				'join'=>$join
				)
			);
		return $this;
	}

	/**
	 * @param string $property
	 * @param mixed $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addGreaterEquals($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(array(
				'sign'=>self::CP_GREATEREQAULS,
				'property'=>$property,
				'value'=>$value,
				'join'=>$join
				)
			);
		return $this;
	}

	/**
	 * @param string $property
	 * @param mixed $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addLessThan($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(Array(
				'sign'=>self::CP_LESSTHAN,
				'property'=>$property,
				'value'=>$value,
				'join'=>$join
				)
			);
		return $this;
	}



	/**
	 * @param string $property
	 * @param ArrayList $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addIn($property, ArrayList $value, $join = self::OP_AND)
	{
		$this->clauses->add(
		array(
		'sign'=>self::CP_IN,
		'property'=>$property,
		'value'=>$value,
		'join'=>$join
		)
		);
		return $this;
	}

	/**
	 * <pre>
	 * $criteria = new Criteria();
	 * $criteria->addNotIn('userName', new ArrayList(1, 2, 3));
	 * </pre>
	 * @param string $property
	 * @param ArrayList $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addNotIn($property, ArrayList $value, $join = self::OP_AND)
	{
		$this->clauses->add(array(
			'sign'=>self::CP_NOTIN,
			'property'=>$property,
			'value'=>$value,
			'join'=>$join));
		return $this;
	}

	/**
	 * 非等価条件を追加する。値が<code>null</code>の場合 IS NOT クエリーが発行されます。
	 * <pre>
	 * $criteria = new Criteria();
	 * $criteria->addNotEquals('userName', 1);
	 * </pre>
	 * @param string $property
	 * @param mixed $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addNotEquals($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(array('sign'=>self::CP_NOTEQUALS, 'property'=>$property,
		'value'=>$value, 'join'=>$join));
		return $this;
	}

	/**
	 * LIKE 条件を追加する
	 * <pre>
	 * $criteria = new Criteria();
	 * $criteria->addBetween('userName', 'Bob%');
	 * </pre>
	 *
	 * @param string $property
	 * @param mixed $value
	 * @param int $join
	 * @return Criteria
	 */
	public function addLike($property, $value, $join = self::OP_AND)
	{
		$this->clauses->add(
		Array(
		'sign'=>self::CP_LIKE,
		'property'=>$property,
		'value'=>$value,
		'join'=>$join
		)
		);
		return $this;
	}

	/**
	 * BETWEEN 条件を追加する
	 * <pre>
	 * $criteria = new Criteria();
	 * $criteria->addBetween('userId', 1, 100);
	 * </pre>
	 *
	 * @param string $property
	 * @param mixed $start
	 * @param mixed $end
	 * @param int $join
	 * @return Criteria
	 */
	public function addBetween($property, $start, $end, $join = self::OP_AND)
	{
		$this->clauses->add(
		Array(
		'sign'=>self::CP_BETWEEN,
		'property'=>$property,
		'value'=>Array('start'=>$start, 'end'=>$end),
		'join'=>$join
		)
		);
		return $this;
	}

	/**
	 * <pre>
	 * $criteria = new Criteria();
	 * $criteria->addExpression('#userId# = 1');
	 * $criteria->addExpression('to_char(#createOn#, 'yyyyMMdd') = ?', new ArrayList('20081011'));
	 * </pre>
	 * @param string $expression
	 * @param ArrayList $params
	 * @param int $join
	 * @return Criteria
	 */
	public function addExpression($expression, ArrayList $params, $join = self::OP_AND)
	{
		$this->clauses->add(array('sign'=>self::CP_EXPRESSION, 'expression'=>$expression,
			'params'=>$params, 'join'=>$join));
		return $this;
	}

	/**
	 * @param Criteria $criteria
	 * @param int $join
	 * @return Criteria
	 */
	public function concat(Criteria $criteria, $join = self::OP_AND)
	{
		$this->clauses->add(array('sign'=>self::CP_CRITERIA, 'value'=>$criteria, 'join'=>$join));
		return $this;
	}

	/**
	 * @return ArrayList
	 */
	public function getClauses()
	{
		return $this->clauses;
	}

}

/**
 * <pre>
 * $order = new Order();
 * $order->add('userId');
 * $order->add('userId', true);
 * $order->add('userId')->add('seq');
 *
 * $order1 = new Order('userId');
 * </pre>
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class Order
{
	/**
	 * @var ArrayList
	 */
	private $orders;

	/**
	 * コンストラクタ
	 * @param ... フィールドのリスト、昇順の場合のみ使用可能
	 * @since 1.2 2009/01/31
	 */
	public function __construct(/*...*/)
	{
		$args = func_get_args();

		$this->orders = new ArrayList();
		foreach ($args as $property) {
			$map = new HashMap();
			$map->put('property', $property);
			$map->put('reverse', false);
			$this->orders->add($map);
		}
	}

	/**
	 * ソート条件を追加する
	 * @param string $property プロパティー名、ネストしたプロパティーを指定する場合は、
	 * 				プロパティー名をカンマで連結します。
	 * @param bool $reverse 降順（DESC）かどうか
	 * @return Order
	 */
	public function add($property, $reverse = false)
	{
		$map = new HashMap();
		$map->put('property', $property);
		$map->put('reverse', $reverse);
		$this->orders->add($map);
		return $this;
	}

//	/**
//	 * @param string $expression
//	 * @return Order
//	 */
//	public function addExpression($expression)
//	{
//		$map = new HashMap();
//		$map->put('property', $property);
//		$map->put('reverse', $reverse);
//		$this->orders->add($map);
//		return $this;
//	}

	/**
	 * すべてのソート条件を返す
	 * @return ArrayList
	 */
	public function getClauses()
	{
		return $this->orders;
	}
}

/**
 *
 * @see Dao
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class MappedLazyProxy
{
	/**
	 * @var Connection
	 */
	private $conn;

	/**
	 * @var Arrary
	 */
	private $mapping;

	/**
	 * @var Arrary
	 */
	private $bean;

	/**
	 * @var mixed
	 */
	private $key;

	/**
	 * @var mixed
	 */
	private $value;


	public function __construct(Connection $conn, $bean, $key, Array $mapping)
	{
		$this->conn = $conn;
		$this->bean = $bean;
		$this->key = $key;
		$this->mapping = $mapping;
		$this->value = null;
	}

	public function __call($method, $args)
	{
		if (method_exists($bean, $method)) {
			if (/*適応するかどうか*/1) {
				// Getterかどうか
				if (substr($method, 0, 2) == 'get') {
					// キーがNULLであれば検索結果もNULLと考える

					if ($ret->key != null && $this->value == null) {
						$property = lcfirst(substr($method, 2, 1)) . substr($method, 3);


						//						$params = new ArrayList();
						$query = "select ";
						$i = 0;
						foreach ($this->mapping['target'] as $column) {
							if ($i++ > 0) $query .= ',';
							$query .= "t0.{$column['column']} as \"t0.{$column['column']}\"";
						}
						$query = "from `{$this->mapping['target']['table']}` as t0";
						$query = " WHERE t0.{$this->mapping['targetKey']} = ?";
						$params->add($this->key);

						$ret = $this->conn->executeQuery($query, new ArrayList($this->key));
						$this->value = $ret;
						return $ret;
					}
					// Setterであればキャッシュを書き換える
				} else if (substr($method, 0, 2) == 'set') {
					//$this->cache = $args[0];
				}
			} else {
				return $bean->$method($args);
			}
		} else {
			// TODO : Exception
		}
	}
}

/**
 *
 * @see RowHandler
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class MappedRowHandler implements RowHandler
{
	/**
	 * @var Object
	 */
	private $proto;

	/**
	 * @var array
	 */
	private $mapping;

	/**
	 * @var TypeHandlerManager
	 */
	private $typeHandlerManager;

	/**
	 * @var Connection
	 */
	private $conn;

	/**
	 * @var bool
	 */
	private $lazyload;

	/**
	 * @var bool
	 */
	static $casting;

	/**
	 * @param Connection $conn
	 * @param Array $mapping
	 * @param bool $lazyload 延滞ロードが必要かどうか
	 */
	public function __construct(Connection $conn, Array $mapping, $lazyload)
	{
		$classLoader = System::getClassLoader();
		$this->lazyload = $lazyload;
		$this->proto = $classLoader->newInstance($mapping['model']);
		$this->mapping = $mapping;
		$this->conn = $conn;
		$this->typeHandlerManager = TypeHandlerManager::getInstance();
		if (self::$casting === null) {
			$config = System::getProperty('MappingDao/force_type_cast');
			self::$casting = ($config !== null ? $config : true);
		}
	}

	/**
	 * @param object $row
	 * @return object
	 */
	public function handle($row)
	{
		$obj = clone $this->proto;

		// Create proxy, if lazy-load enabled.
		if ($this->lazyload) {
			$obj = new MappedLazyProxy($obj);
		}

		foreach ($row as $key => $value) {

			// キーはスキーマー名.カラム名で指定される
			$list = explode('.', strtolower($key));
			$scheme = $list[0];
			$property = isset($list[1]) ? $list[1] : '';

			$columnIndex = $this->mapping['_columnIndex'];
			if (isset($columnIndex[$scheme]) && isset($columnIndex[$scheme]['columns'][$property])) {
				// Get column defination.
				$column = $columnIndex[$scheme]['columns'][$property];
				// Process type-handler.
				if (isset($column['handler'])) {
					$handler = $this->typeHandlerManager->find($column['handler']);
					if ($handler == null) {
						$message = "SQL型ハンドラー '{$column['handler']}' が見つかりません。";
						throw new SQLException($message);
					}
					$value = $handler->toPropertyValue($value);
				} else if ($value !== null && self::$casting && isset($column['phpType'])) {
					switch ($column['phpType']) {
					case 'int':
						$value = is_int($value) ? $value : intval($value);
						break;
					case 'string':
						$value = is_string($value) ? $value : strval($value);
						break;
					default:
						$message = "PHP型 '{$column['phpType']}' はサポートされていません。";
						throw new SQLException($message);
					}
				}

				// Set values.
				$bean = $obj;
				if ($columnIndex[$scheme]['property'] != null) {
					if ($value !== null) {
						$newobj = BeanUtils::getProperty($bean, $columnIndex[$scheme]['property']);
						if ($newobj === null) {
							$classLoader = System::getClassLoader();
							$newobj = $classLoader->newInstance($columnIndex[$scheme]['model']);
							BeanUtils::setProperty($bean, $columnIndex[$scheme]['property'], $newobj);
						}
						$bean = $newobj;
					}
				}
				if ($bean != null) {
					BeanUtils::setProperty($bean, $column['property'], $value);
				}
			}
		}

		return $obj;
	}
}

/**
 * データベースの型とプロパティーの型をマッピングするためのインターフェース
 * <p/>
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
interface TypeHandler
{
	/**
	 * @param mixed $value
	 * @return mixed
	 */
	public function toStatementValue($value);

	/**
	 * @param mixed $value
	 * @return mixed
	 */
	public function toPropertyValue($value);
}

/**
 * OPEN_FLAG VARCHAR(1)のような文字列で扱われるSQLカラムをbool型で
 * マップピングするための型ハンドラ
 * <p/>
 *
 * @see TypeHandler
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class StringToBoolTypeHandler implements TypeHandler
{
	/**
	 * @param bool $value
	 * @return string
	 */
	public function toStatementValue($value)
	{
		return ($value ? "1" : "0");
	}

	/**
	 * @param bool $value
	 * @return string
	 */
	public function toPropertyValue($value)
	{
		if ($value !== null && $value === "1") {
			return true;
		} else {
			return false;
		}
	}
}


/**
 * OPEN_FLAG VARCHAR(1)のような文字列で扱われるSQLカラムをbool型で
 * マップピングするための型ハンドラ
 * <p/>
 *
 * @see TypeHandler
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class NumberToBoolTypeHandler implements TypeHandler
{
	/**
	 * @param bool $value
	 * @return string
	 */
	public function toStatementValue($value)
	{
		return ($value ? 1 : 0);
	}

	/**
	 * @param bool $value
	 * @return string
	 */
	public function toPropertyValue($value)
	{
		if ($value !== null && $value == 1) {
			return true;
		} else {
			return false;
		}
	}
}

/**
 * @see TypeHandler
 * @version 1.0 2009/03/20
 */
class DateToDateTimeTypeHandler implements TypeHandler
{
	/**
	 * @param DateTime $value
	 * @return string
	 * @see TypeHandler#toStatementValue
	 */
	public function toStatementValue($value)
	{
		return ($value === null ? null : $value->format('Y-m-d'));
	}

	/**
	 * @param bool $value
	 * @return Timestamp
	 * @see TypeHandler#toPropertyValue
	 */
	public function toPropertyValue($value)
	{
		return ($value !== null ? Date::parse($value) : null);
	}
}

/**
 * @see TypeHandler
 * @package testApp
 */
class DateToBargeTimestampTypeHandler implements TypeHandler
{
	/**
	 * @param Timestamp $value
	 * @return string
	 * @see TypeHandler#toStatementValue
	 */
	public function toStatementValue($value)
	{
		return ($value === null ? null : $value->format('Y-m-d H:i:s'));
	}

	/**
	 * @param bool $value
	 * @return Timestamp
	 * @see TypeHandler#toPropertyValue
	 */
	public function toPropertyValue($value)
	{
		return ($value !== null ? new Timestamp($value) : null);
	}
}

/**
 * 型ハンドラを管理するクラス
 *
 * @version 1.0
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
class TypeHandlerManager
{
	/**
	 * @var TypeHandlerManager
	 */
	private static $instance;

	/**
	 * Enter description here...
	 *
	 * @var HashMap
	 */
	private $handlers;

	private function __construct()
	{
		$this->handlers = new HashMap();
		$this->registerInstance("str2bool", new StringToBoolTypeHandler());
		$this->registerInstance("num2bool", new NumberToBoolTypeHandler());
		$this->registerInstance("date2datetime", new DateToDateTimeTypeHandler());
		$this->registerInstance("date2bgtimestamp", new DateToBargeTimestampTypeHandler());
	}

	/**
	 * @return TypeHandlerManager
	 */
	public static function getInstance()
	{
		if (self::$instance == null) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * @param string $key
	 * @return TypeHandler
	 */
	public function find($key)
	{
		return $this->handlers->get($key);
	}

	/**
	 * @param string $key
	 * @return TypeHandler
	 */
	public function registerInstance($key, $instance)
	{
		$this->handlers->put($key, $instance);
	}

	/**
	 * @param string $key
	 * @return TypeHandler
	 */
	public function registerClass($key, $class)
	{
		$classLoader = System::getClassLoader();
		$instance = $classLoader->newInstance($class);
		$this->handlers->put($key, $instance);
	}
}

/**
 * @version 1.0
 * @package barge
 * @author Kenji Namba
 * @since Barge 1.0 2008/06/11
 */
interface QueryDiarect
{
	/**
	 * @param string $seqnm
	 * @return string
	 */
	public function getSelectSequence($seqnm);

//	/**
//	 * @param string $table
//	 * @return string
//	 */
//	public function existsTable(Connection $conn, $table);
}

/**
 * @version 1.0
 * @package barge
 * @author Kenji Namba
 * @since Barge 1.0 2008/06/11
 */
class PgsqlQueryDiarect implements QueryDiarect
{
	/**
	 * @param string $seqnm
	 * @return string
	 */
	public function getSelectSequence($seqnm)
	{
		return "select nextval('".$seqnm."')";;
	}

//	/**
//	 * @param string $table
//	 * @return string
//	 */
//	public function existsTable(Connection $conn, $table)
//	{
//		$table = strtoupper($table);
//		$list = $conn->executeQuery("show tables");
//		foreach ($list as $map) {
//			if (strtoupper($map->get('Tables_in_catalog')) === $table) {
//				return true;
//			}
//		}
//		return false;
//	}
}

/**
 * @version 1.0
 * @package barge
 * @author Kenji Namba
 * @since Barge 1.0 2008/06/11
 */
class MysqlQueryDiarect implements QueryDiarect
{
	/**
	 * @param string $seqnm
	 * @return string
	 */
	public function getSelectSequence($seqnm)
	{
		$message = '';
		throw new IllegalArgumentException($message);
	}

//	/**
//	 * @param string $table
//	 * @return string
//	 */
//	public function existsTable(Connection $conn, $table)
//	{
//		$message = '';
//		throw new IllegalArgumentException($message);
//	}
}

/**
 * @see MappedDao
// * @version 1.0
// * @package barge
// * @author Kenji Namba
// * @since Barge 1.0 2006/09/11
// */
//class DefinitionMappedDao extends MappedDao
//{
//	/**
//	 * @var DaoDefinition
//	 */
//	protected $definition;
//
//	/**
//	 * コンストラクタ
//	 *
//	 * @param Connection $conn
//	 */
//	public function __construct(Connection $conn, DaoDefinition $definition)
//	{
//		$this->definition = $definition;
//		parent::__construct($conn);
//	}
//
//	public function getMapping()
//	{
//		return $this->definition->getDefinition();
//	}
//}


/**
 * SQLマップ定義より追加、登録、検索、更新、削除をサポートするDAOを生成する
 *
 * @see Dao
 * @version 1.0
 * @package barge
 * @author Kenji Namba
 * @since Barge 1.0 2006/09/11
 */
abstract class MappedDao extends Dao
{
	/**
	 * @var array
	 */
	private $mapping;

	/**
	 * @var TypeHandlerManager
	 */
	private $typeHandlerManager;

	/**
	 * @var QueryDiarect
	 */
	private $queryDiarect;

	/**
	 * コンストラクタ
	 *
	 * @param Connection $conn
	 * @param QueryDiarect $queryDiarect
	 */
	public function __construct(Connection $conn, QueryDiarect $queryDiarect = null)
	{
		parent::__construct($conn);
		$this->mapping = $this->createLink($this->getMapping());
		$this->typeHandlerManager = TypeHandlerManager::getInstance();
		$this->queryDiarect = $queryDiarect;
	}

	/**
	 * 内部キャッシュを作成します
	 *
	 * @param array $mapping マッピング定義配列
	 * @return Array
	 */
	private function createLink(Array $mapping)
	{
		/*
		mapping = {
			_prefix : 't0',
			_propertyIndex : {
				userId   : @REF(/columns/column),
				userName : @REF(/columns/column),
				sect     : {
					_propertyIndex : {
						...
					},
					_prefix : 't1'
				},
			},
			_columnIndex   : {
				t0 : {
					columns  : {
						user_id   : @REF(/columns/column),
						user_name : @REF(/columns/column),
					}
				},
				t1 : {
					columns  : {
						user_id   : @REF(/columns/column),
						user_name : @REF(/columns/column),
					},
					property : 'user',
					model : 'User'
				},
			},
		}
		 */
		$t = 0;
		$columns = array();
		$mapping['_propertyIndex'] = array();
		$prefix = "t{$t}";
		$mapping['_prefix'] = $prefix;
		$mapping['_columnIndex'] = array();
		$mapping['_columnIndex'][$prefix] = array('columns'=>array(), 'property'=>null);
		$t++;
		foreach ($mapping['columns'] as $column) {
			$mapping['_columnIndex']['t0']['columns'][strtolower($column['column'])] = $column;
			if ($column['type'] == 'relating-property') {
				$prefix = "t{$t}";
				$column['relation']['_prefix'] = $prefix;
				$mapping['_propertyIndex'][$column['property']] = array(
					'_propertyIndex' => array(),
					'_prefix' => $prefix,
					);
				$mapping['_columnIndex'][$prefix] = array(
					'columns' => array(),
					'property' => $column['property'],
					'model' => $column['relation']['target']['model']);
				foreach ($column['relation']['target']['columns'] as $column2) {
					$mapping['_columnIndex'][$prefix]['columns'][strtolower($column2['column'])] = $column2;
					$mapping['_propertyIndex'][$column['property']]['_propertyIndex'][$column2['property']] = $column2;
				}
				$t++;
			} else {
				$mapping['_propertyIndex'][$column['property']] = $column;
			}
			array_push($columns, $column);
		}

		$mapping['columns'] = $columns;
		return $mapping;
	}

	/**
	 * @param Bean $obj
	 * @return int
	 */
	public function regist(Bean $obj)
	{
		$params = new ArrayList();
		$sequences = new ArrayList();
		$query = '';
		$query .= 'insert into ';
		$query .= $this->mapping['table'];
		$query .= ' (';
		$i =0;
		foreach ($this->mapping['columns'] as $column) {
			if (isset($column['insert']) && $column['insert'] === false) continue;
			if ($i++ > 0) $query .= ',';
			$query .= $column['column'];
		}
		// values
		$query .= ') values (';
		$i =0;
		foreach ($this->mapping['columns'] as $column) {
			if (isset($column['insert']) && $column['insert'] === false) {
				// Skip
			} else {
				if ($i > 0) $query .= ',';
				if (isset($column['generator'])) {
					if ($column['generator']['type'] == 'sequence') {
						$sequence = $column['generator']['params'][0];
						$query .= '(' . $this->queryDiarect->getSelectSequence($sequence) . ')';
					} else if ($column['generator']['type'] == 'auto-increment') {	// TODO :
						$query .= "''";	// Dummy
						$sequences->add($column);
					} else {
						throw new Exception("Unsupport {$column['generator']['type']}");
					}
				} else {
					$value = null;
					if ($column['type'] == 'timestamp') {
						$value = date(isset($column['timestamp']['format']) ?
							$column['timestamp']['format'] : 'Y-m-d H:i:s');
					} else if ($column['type'] == 'relating-property') {
						// PKを検索
						$bean = BeanUtils::getProperty($obj, $column['property']);
						if ($bean != null) {
							$pkcolumn = null;
							foreach ($column['relation']['target']['columns'] as $column2) {
								if ($column2['column'] == $column['relation']['targetKey'])
								$pkcolumn = $column2;
							}
							if ($pkcolumn == null) {
								$message = "プライマリーキー(id)が定義されていません。";
								throw new SQLException($message);
							}
							$value = BeanUtils::getProperty($bean, $pkcolumn['property']);
						} else {
							$value = null;
						}
					} else {
						$value = BeanUtils::getProperty($obj, $column['property']);
						if (isset($column['handler'])) {
							$handler = $this->typeHandlerManager->find($column['handler']);
							if ($handler == null) {
								$message = "SQL型ハンドラー '{$column['handler']}' が見つかりません。";
								throw new SQLException($message);
							}
							$value = $handler->toStatementValue($value);
						}
					}
					$query .= '?';
					$params->add($value);
				}
				$i++;
			}
		}
		$query .= ')';
		$ret = $this->getConnection()->executeUpdate($query, $params);
		// 発番ルールが指定されているものは値を設定
		foreach ($sequences as $_seq) {
			$obj->__set($_seq['property'],
			$this->getConnection()->getLastInsertId($_seq['property']['column']));
		}
		return $ret;
	}

//	/**
//	 * テーブルが存在するかどうかを検索する
//	 * @param string $table テーブル名
//	 * @return unknown
//	 */
//	public function existsTable($table)
//	{
//		$conn = $this->getConnection();
//		return $this->existsTable($conn, $table);
//	}

	/**
	 * ＰＫ検索により事前チェックを行い自動的に登録／更新クエリーを発行します
	 *
	 * @param Bean $obj
	 * @return int 更新によって作用した行数
	 */
	public function registOrUpdate(Bean $obj)
	{
		$primaries = $this->primaryList();
		$criteria = new Criteria();
		for ($i = 0; $i < count($primaries); $i++) {
			$criteria->addEquals($primaries[$i]['property'],
				BeanUtils::getProperty($obj, $primaries[$i]['property']),
				Criteria::OP_AND);
		}

		$list = $this->findBy($criteria);
		if ($list->size() == 0) {
			return $this->regist($obj);
		} else if ($list->size() == 1) {
			return $this->update($obj);
		} else {
			$message = "複数の更新対象レコードが選択されました。";
			throw new SQLException($message);
		}
	}

	/**
	 * @param Bean $obj
	 * @return int 更新によって作用した行数
	 */
	public function update(Bean $obj)
	{
		$query = '';
		$value = null;
		$params = new ArrayList();
		$primaries = $this->primaryList();
		$query .= 'update ';
		$query .= $this->mapping['table'];
		$query .= ' set ';
		$i =0;
		foreach ($this->mapping['columns'] as $column) {
			if ($column["type"] == 'id' || (isset($column['update']) && $column['update'] == false)) {
				// Skip
			} else {
				if ($i > 0) $query .= ',';
				if ($column['type'] == 'timestamp') {
					$value = date(isset($column['timestamp']['format']) ?
					$column['timestamp']['format'] : 'Y-m-d H:i:s');
				} else if ($column['type'] == 'relating-property') {
					$bean = BeanUtils::getProperty($obj, $column['property']);
					if ($bean != null) {
						// PKを検索
						$pkcolumn = null;
						foreach ($column['relation']['target']['columns'] as $column2) {
							if ($column2['column'] == $column['relation']['targetKey'])
							$pkcolumn = $column2;
						}
						if ($pkcolumn == null) {
							$message = "プライマリーキー(id)が定義されていません。";
							throw new SQLException($message);
						}
						$value = BeanUtils::getProperty($bean, $pkcolumn['property']);
					} else {
						$value = null;
					}
				} else {
					$value = BeanUtils::getProperty($obj, $column['property']);
					// Check handler.
					if (isset($column['handler'])) {
						$handler = $this->typeHandlerManager->find($column['handler']);
						if ($handler == null) {
							$message = "SQL型ハンドラー '{$column['handler']}' が見つかりません。";
							throw new SQLException($message);
						}
						$value = $handler->toStatementValue($value);
					}
				}
				$query .= $column['column'] . '= ?';
				$params->add($value);
				$i++;
			}
		}
		// where
		$query .= ' where ';
		$i =0;
		foreach ($primaries as $pk) {
			if ($i++ > 0) $query .= 'and ';
			$query .= $pk['column'] . ' = ?';
			$value = BeanUtils::getProperty($obj, $pk['property']);
			if (isset($pk['handler'])) {
				$handler = $this->typeHandlerManager->find($pk['handler']);
				if ($handler == null) {
					$message = "SQL型ハンドラー '{$pk['handler']}' が見つかりません。";
					throw new SQLException($message);
				}
				$value = $handler->toStatementValue($value);
			}
			$params->add($value);
		}
		return $this->getConnection()->executeUpdate($query, $params);
	}

	/**
	 * @param Bean $obj
	 * @return int 更新によって作用した行数
	 */
	public function delete(Bean $obj)
	{
		$query = '';
		$params = new ArrayList();
		$primaries = $this->primaryList();
		$query .= 'delete from ';
		$query .= '`'.$this->mapping['table'].'`';
		// where
		$query .= ' where ';
		$i =0;
		foreach ($primaries as $pk) {
			if ($i++ > 0) $query .= 'and ';
			$query .= $pk['column'] . ' = ?';
			$params->add(BeanUtils::getProperty($obj, $pk['property']));
		}
		return $this->getConnection()->executeUpdate($query, $params);
	}


	/**
	 * @param ... プライマリーキーに対応した引数リスト、末尾は延滞ロードを使用するかどうか
	 * 		省略した場合は<code>false</code>
	 * {@return object}
	 * @throws SQLException SQL で発生した例外、マッピングされたキーの個数と一致しない
	 */
	public function get(/*...*/)
	{
		$args = func_get_args();
		$primaries = $this->primaryList();
		$criteria = new Criteria();
		$lazyload = false;

		$argsNum = count($args);
		if ($argsNum == count($primaries)) {
			$lazyload = false;
//		} else if ($argsNum == (count($primaries) + 1)) {
//			$lazyload = $primaries[count($primaries) - 1];
//			$args = array_splice($args, 0, -1);
//			$argsNum--;
		} else {
			$message = "指定されたパラメータは、テーブル '"
			.$this->mapping['table']
			."' のキーよりも多すぎます。";
			throw new SQLException($message);
		}

		for ($i = 0; $i < $argsNum; $i++) {
			$criteria->addEquals($primaries[$i]['property'],
			$args[$i], Criteria::OP_AND);
		}

		$list = $this->findBy($criteria);
		if ($list->size() > 0) {
			return $list->get(0);
		} else {
			return null;
		}
	}

	/**
	 * 条件検索
	 *
	 * @param Criteria $criteria
	 * @param Order $order[optional]
	 * @param bool $lazyload 延滞ロードを使用するかどうか
	 * @param $limit
	 * @param $offset = 0
	 * @return ArrayList
	 */
	public function findBy(Criteria $criteria = null, Order $order = null, $limit = null, $offset = 0)
	{
		$query = "";
		$needLazyload = false;
		$prefix = $this->mapping['_prefix'];
		$params = new ArrayList();
		$clauses = $criteria != null ? $criteria->getClauses() : new ArrayList();
		$orders = ($order !== null ? $order->getClauses() : new ArrayList());

		$query .= "select ";
		$fromQuery = " from `{$this->mapping['table']}` as {$prefix}";
		$i =0;
		foreach ($this->mapping['columns'] as $column) {
			if ($i++ > 0) {
				$query .= ',';
			}
			// リレーション
			if ($column['type'] == 'relating-property') {
				// Lazy-loadが設定されている場合と、関連タイプが one-to-manyの場合はJOINしない
				if ($column['relation']['type'] === 'one-to-many') {
					$needLazyload = true;
				} else if ($column['relation']['type'] === 'one-to-one' &&
						isset($column['relation']['lazyLoad']) &&
						$column['relation']['lazyLoad'] === true) {
					$needLazyload = true;
				} else {
					$j = 0;
					$subPrefix = $column['relation']['_prefix'];
					foreach ($column['relation']['target']['columns'] as $column2) {
						if ($j++ > 0) $query .= ',';
						$query.= "{$subPrefix}.{$column2['column']} as \"{$subPrefix}.{$column2['column']}\"";
					}
					// JOIN
					if (isset($column['notNull']) && $column['notNull'] === true) {
						$fromQuery .= " inner join {$column['relation']['target']['table']} as {$subPrefix} on"
							." ({$prefix}.{$column['relation']['localKey']} = {$subPrefix}.{$column['relation']['targetKey']})";
					} else {
						$fromQuery .= " left join {$column['relation']['target']['table']} as {$subPrefix} on"
							." ({$prefix}.{$column['relation']['localKey']} = {$subPrefix}.{$column['relation']['targetKey']})";
					}
				}
			} else {
				// t0.user_id as "t0.user_id"
				$query.= "{$prefix}.{$column['column']} as \"{$prefix}.{$column['column']}\"";
			}
		}
		$query .= $fromQuery;
		if ($clauses->hasElement()) {
			$query .= ' WHERE ';
			$query .= $this->whereQuery($clauses, $params, $this->mapping);
		}
		if ($orders->hasElement()) {
			$query .= ' ORDER BY ';
			$i = 0;
			foreach ($orders as $order) {
				if ($i++ > 0) $query .= ',';
				$nested = null;
				$property = $order->get('property');
				if (($p = strpos($property, '.')) !== false) {
					$nested = substr($property, $p + 1);
					$property = substr($property, 0, $p);
				}
				// カラム定義を取得する
				if ($nested !== null) {
					if (! array_key_exists($property, $this->mapping['_propertyIndex']) ||
					! array_key_exists($nested, $this->mapping['_propertyIndex'][$property]['_propertyIndex'])) {
						$message = "ソートフィールド '{$property}.{$nested}' というプロパティーは存在しません。";
						throw new SQLException($message);
					}
					$column = $this->mapping['_propertyIndex'][$property]['_propertyIndex'][$nested];

					$nestedprefix = $this->mapping['_propertyIndex'][$property]['_prefix'];
					$query .= $nestedprefix.'.'.$column['column'];
					$query .= ($order->get('reverse') ? ' desc' : '');

				} else {
					if (! array_key_exists($property, $this->mapping['_propertyIndex'])) {
						$message = "ソートフィールド '{$property}' というプロパティーは存在しません。";
						throw new SQLException($message);
					}
					$query .= $prefix.'.'.$this->mapping['_propertyIndex'][$order['property']]['column'];
					$query .= ($order['reverse'] ? ' desc' : '');
				}
			}
		}
		if ($limit !== null) {
			$query .= ' limit '.$limit;
		}
		if ($offset > 0) {
			$query .= ' offset '.$offset;
		}
		$rowHandler = new MappedRowHandler($this->getConnection(), $this->mapping, $needLazyload);
		return $this->getConnection()->executeQuery($query, $params, $rowHandler);
	}


//	/**
//	 * @param Bean $obj
//	 * @return int
//	 */
//	public function count()
//	{
//	}

	/**
	 * @param Criteria $criteria
	 * @return int
	 * @since Barge 1.2
	 */
	public function deleteBy(Criteria $criteria = null)
	{
		$query = "";
		$prefix = $this->mapping['_prefix'];
		$params = new ArrayList();
		$clauses = $criteria != null ? $criteria->getClauses() : new ArrayList();

		$query .= "delete {$prefix}";
		$fromQuery = " from `{$this->mapping['table']}` as {$prefix}";
		$i =0;
		foreach ($this->mapping['columns'] as $column) {
			// リレーション
			if ($column['type'] == 'relating-property') {
				// Lazy-loadが設定されている場合と、関連タイプが one-to-manyの場合はJOINしない
				if ($column['relation']['type'] === 'one-to-many') {
				} else if ($column['relation']['type'] === 'one-to-one' &&
						isset($column['relation']['lazyLoad']) &&
						$column['relation']['lazyLoad'] === true) {
				} else {
					$j = 0;
					$subPrefix = $column['relation']['_prefix'];
					// JOIN
					if (isset($column['notNull']) && $column['notNull'] === true) {
						$fromQuery .= " inner join {$column['relation']['target']['table']} as {$subPrefix} on"
							." ({$prefix}.{$column['relation']['localKey']} = {$subPrefix}.{$column['relation']['targetKey']})";
					} else {
						$fromQuery .= " left join {$column['relation']['target']['table']} as {$subPrefix} on"
							." ({$prefix}.{$column['relation']['localKey']} = {$subPrefix}.{$column['relation']['targetKey']})";
					}
				}
			}
		}
		$query .= $fromQuery;
		if ($clauses->hasElement()) {
			$query .= ' where ';
			$query .= $this->whereQuery($clauses, $params, $this->mapping);
		}

		return $this->getConnection()->executeUpdate($query, $params);
	}

	/**
	 * 検索条件クエリーを作成する
	 *
	 * @param ArrayList $clauses
	 * @param ArrayList $params
	 * @param array $mapping
	 * @return string
	 */
	private function whereQuery(ArrayList $clauses, ArrayList $params,
	array $mapping)
	{
		$prefix = $mapping['_prefix'];
		$groupStart = true;
		$query = '';
		$i = 0;
		foreach ($clauses as $criterion) {
			/* @var $criterion Array */
			if ($criterion['sign'] === Criteria::CP_EXPRESSION) {
				if ($i > 0) {
					if ($criterion['join'] === Criteria::OP_AND) {
						$query .= 'and ';
					} else if ($criterion['join'] === Criteria::OP_OR) {
						$query .= 'or ';
					}
				}
				if ($groupStart) {
					$query .= '(';
					$groupStart = false;
				}
				$query .= $this->parseExpression($criterion['expression'],
					$this->mapping['_propertyIndex'],
					$prefix);
				$params->addAll($criterion['params']);

			} else if ($criterion['sign'] === Criteria::CP_CRITERIA) {
				if (!$groupStart) {
					$query .= ') ';
				}
				if ($i > 0) {
					if ($criterion['join'] === Criteria::OP_AND) {
						$query .= 'and ';
					} else if ($criterion['join'] === Criteria::OP_OR) {
						$query .= 'or ';
					}
				}
				$query .= $this->whereQuery($criterion['value']->getClauses(), $params, $mapping);
				$groupStart = true;
			} else {
				if ($i > 0) {
					if ($criterion['join'] === Criteria::OP_AND) {
						$query .= 'and ';
					} else if ($criterion['join'] === Criteria::OP_OR) {
						$query .= 'or ';
					}
				}
				if ($groupStart) {
					$query .= '(';
					$groupStart = false;
				}
				$nested = null;
				$property = $criterion['property'];
				if (($p = strpos($property, '.')) !== false) {
					$nested = substr($property, $p + 1);
					$property = substr($property, 0, $p);
				}

				// カラム定義を取得する
				$proprtystx = null;
				if ($nested !== null) {
					if (! array_key_exists($property, $this->mapping['_propertyIndex']) ||
					! array_key_exists($nested, $this->mapping['_propertyIndex'][$property]['_propertyIndex'])) {
						$message = "検索フィールド '{$property}.{$nested}' というプロパティーは存在しません。";
						throw new SQLException($message);
					}
					$column = $this->mapping['_propertyIndex'][$property]['_propertyIndex'][$nested];
					$nestedprefix = $this->mapping['_propertyIndex'][$property]['_prefix'];
					$proprtystx .= "{$nestedprefix}.{$column['column']}";
				} else {
					if (! array_key_exists($property, $this->mapping['_propertyIndex'])) {
						$message = "検索フィールド '{$property}' というプロパティーは存在しません。";
						throw new SQLException($message);
					}
					$column = $this->mapping['_propertyIndex'][$property];
					$proprtystx .= "{$prefix}.{$column['column']}";
				}

				switch ($criterion['sign']) {
					case Criteria::CP_EQUAL:
						if ($criterion['value'] === null) {
							$query .= $proprtystx.' is null ';
						} else {
							$query .= $proprtystx.' =? ';
							$params->add($this->toSqlType($criterion['value'], $column));
						}
						break;
					case Criteria::CP_LESSTHAN:
						$query .= $proprtystx.' < ? ';
						$params->add($this->toSqlType($criterion['value'], $column));
						break;
					case Criteria::CP_GREATERTHAN:
						$query .= $proprtystx.' > ? ';
						$params->add($this->toSqlType($criterion['value'], $column));
						break;
					case Criteria::CP_LESSEQALS:
						$query .= $proprtystx.' <= ? ';
						$params->add($this->toSqlType($criterion['value'], $column));
						break;
					case Criteria::CP_GREATEREQAULS:
						$query .= $proprtystx.' >= ? ';
						$params->add($this->toSqlType($criterion['value'], $column));
						break;
					case Criteria::CP_NOTEQUALS:
						if ($criterion['value'] === null) {
							$query .= $proprtystx.' is not null ';
						} else {
							$query .= $proprtystx.' <>? ';
							$params->add($this->toSqlType($criterion['value'], $column));
						}
						break;
					case Criteria::CP_IN:
					case Criteria::CP_NOTIN:
						// TODO : 値が NULL の場合は、 IS NULL OR IN (...) を発行する必要あり？
						if ($criterion['value'] && count($criterion['value']) > 0) {
							if ($criterion['sign'] == Criteria::CP_NOTIN) {
								$query .= $proprtystx.' NOT IN (';
							} else {
								$query .= $proprtystx.' IN (';
							}
							$j = 0;
							foreach ($criterion['value'] as $value) {
								if ($j++ > 0) $query .= ',';
								$query .= '?';
								$params->add($this->toSqlType($value, $column));
							}
							$query .= ')';
						} else {
							// Note : 1 or 0 はSQL上で論理値の単一項として扱われる
							if ($criterion['sign'] == Criteria::CP_NOTIN) {
								$query .= '1';
							} else {
								$query .= '0';
							}
						}
						break;
					case Criteria::CP_LIKE:
						$query .= $proprtystx.' like ? ';
						$params->add($criterion['value']);
						break;
					case Criteria::CP_BETWEEN:
						$query .= $proprtystx.' between ? and ? ';
						$params->add($this->toSqlType($criterion['value']['start'], $column));
						$params->add($this->toSqlType($criterion['value']['end'], $column));
						break;
				}
			}
			$i++;
		}
		if ($i > 0 && !$groupStart) {
			$query .= ')';
		}
		return $query;
	}

	/**
	 * 式をクエリーに変換します。
	 *
	 * @param string $expresson
	 * @param array $propertyToColumn
	 * @return Array
	 */
	private function parseExpression($expresson, Array $propertyToColumn, $prefix)
	{
		$start = 0;
		$end = 0;
		$query = "";
		while (($start = strpos($expresson, '#', $end)) !== false) {
			$query .= substr($expresson, $end, $start - $end);
			if (false === ($end = strpos($expresson, '#', $start + 1))) break;
			$propName = substr($expresson, $start +1, $end - $start -1);
//var_dump($propName);
			$query.= "{$prefix}.{$propertyToColumn[$propName]['column']}";
			$start = ++ $end;
		}
		$query .= substr($expresson, $end);
		return $query;
	}

	/**
	 * @param mixed $value
	 * @param Array $column
	 * @return mixed
	 */
	private function toSqlType($value, $column)
	{
		if (isset($column['handler'])) {
			$handler = $this->typeHandlerManager->find($column['handler']);
			if ($handler == null) {
				$message = "SQL型ハンドラー '{$column['handler']}' が見つかりません。";
				throw new SQLException($message);
			}
			$value = $handler->toStatementValue($value);
		}
		return $value
		;
	}

	/**
	 * @return Array
	 */
	private function primaryList()
	{
		//array_f
		$selectList = array();
		foreach ($this->mapping['columns'] as $column) {
			if ($column['type'] == 'id')
			array_push($selectList, $column);
		}
		return $selectList;
	}

	abstract protected function getMapping();
}


?>